<?php
/**
 * The SIDN EPP API
 *
 * <Insert random introduction here...>
 * cat /dev/urandom > introduction
 *
 *
 * @author Ralph de Boom <php5-sidnepp@mijndomein.nl>
 * @author Peter Keizer <php5-sidnepp@mijndomein.nl>
 * @license http://www.gnu.org/licenses/gpl-3.0.txt GNU Public License v3
 * @copyright http://www.mijndomein.nl/
 * @version 0.5
 * @link http://php5-sidnepp.svn.sourceforge.net/viewvc/php5-sidnepp/trunk/dev/docs/index.html
 * @see http://php5-sidnepp.svn.sourceforge.net/viewvc/php5-sidnepp/trunk/dev/docs/php5-sidnepp/SidnEppApi.html
 * @package php5-sidnepp
  */

include_once(dirname(__FILE__).'/sidnEppConfig.php');
include_once(dirname(__FILE__).'/sidnEppException.php');
include_once(dirname(__FILE__).'/sidnEppResult.php');
include_once(dirname(__FILE__).'/sidnTools.php');
include_once(dirname(__FILE__).'/sidnDomain.php');
include_once(dirname(__FILE__).'/sidnDomainInfo.php');
include_once(dirname(__FILE__).'/sidnDomainInfoResult.php');
include_once(dirname(__FILE__).'/sidnContactInfoResult.php');
include_once(dirname(__FILE__).'/sidnContactInfo.php');
include_once(dirname(__FILE__).'/sidnContact.php');
include_once(dirname(__FILE__).'/sidnContactPostalInfo.php');
include_once(dirname(__FILE__).'/sidnCreateContactResult.php');
include_once(dirname(__FILE__).'/sidnNameserverInfo.php');
include_once(dirname(__FILE__).'/sidnNameserverInfoResult.php');
include_once(dirname(__FILE__).'/sidnTransferInfo.php');
include_once(dirname(__FILE__).'/sidnTransferInfoResult.php');
include_once(dirname(__FILE__).'/sidnResponseMessage.php');
include_once(dirname(__FILE__).'/eppProtocol.php');

class SidnEppApi extends EppProtocol
{
	/**
	 * EPP Server name for use for validating if the server runs a compatible version with this client;
	 * @var string
	 */
	var $eppServerName = SidnEppConfig::SERVER_NAME;

	/**
	 * EPP Server version for use for validating if the server runs a compatible version with this client;
	 * @var string
	 */
	var $eppServerVersion = '1.0';

	/**
	 * EPP Server build version for use for validating if the server runs a compatible version with this client;
	 * @var string
	 */
	var $eppServerBuildVersion = 'http://rxsd.domain-registry.nl/sidn-ext-epp-1.0';

	/**
	 * Connection state
	 * @var string
	 */
	var $connectionState;

	const PHP_VERSION = '5.2.6';

	/**
	 * Several connection states
	 */
	const EPP_NOT_CONNECTED     = 0;
	const EPP_CONNECTED         = 5;
	const EPP_HELLO_RECEIVED    = 10;
	const EPP_HELLO_SENT        = 15;
	const EPP_VALIDATING        = 20;
	const EPP_VALIDATED         = 25;
	const EPP_LOGGING_IN        = 30;
	const EPP_LOGGED_IN         = 35;

	/**
	 * Several global return codes
	 */
	const EPP_COMMAND_SUCCESSFUL		= 1000;
	const EPP_COMMAND_TRANSFER_PENDING	= 1001;
	const EPP_COMMAND_POLLED_OK			= 1301;
	const EPP_COMMAND_LOGOUT_OK			= 1500;
	const EPP_COMMAND_SYNTAX_ERROR		= 2001;

	/**
	 * SIDN Specific constants
	 */
	const SIDN_LEGAL_FORM_ORGANISATION	= 'BV';
	const SIDN_LEGAL_FORM_PERSON		= 'PERSOON';

 	/**
	 * Constants for IP versions
	 */
	const IP_V4 = 'v4';
	const IP_V6 = 'v6';

	/**
	 * constructor
	 */
	public function __construct()
	{
		// Check if the version is correct
		if (version_compare(PHP_VERSION, self::PHP_VERSION, '<'))
		{
			die('Please use the SIDN EPP only with PHP '.self::PHP_VERSION.' or higher');
		}
	}

	/**
	 * destructor
	 */
	public function __destruct()
	{
		$this->doDisconnect();
	}

	/**
	 * delete a contacthandle
	 *
	 * @param SidnContact $contact
	 * @return SidnEppResult
	 */
	public function deleteContact(SidnContact $contact)
	{

		// fill template
		$vars = array();
		$vars['contactid'] = $contact->getContactHandle();

		// send command
		$result = $this->writeAndRead('delete_contact.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// success
		return $this->returnSuccessResult($result);
	}

	/**
	 * This will log you into the SIDN EPP server and checks if it's ready for command processing...
	 *
	 * @return SidnEppResult
	 */
	public function isAvailable()
	{
		if (!$this->doConnect())
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'Connecting to SIDN has failed');
		}

		$this->validateConnection();

		if ($this->connectionState != self::EPP_VALIDATED || !$this->doLogin())
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'Validation failed');
		}

		if ($this->connectionState == self::EPP_LOGGED_IN)
		{
			return new SidnEppResult(SidnEppResult::SUCCESS);
		}
		else
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'SIDN is not available at this time.');
		}
	}

	/**
	 * This will return the info of the given domainname
	 * @param SidnDomain $domain
	 * @return SidnDomainInfoResult
	 */
	public function getDomainInfo(SidnDomain $domain)
	{
		// fill template
		$vars = array();
		$vars['domainname'] = $domain->getDomainName();
		
		// send command
		$result = $this->writeAndRead('info_domain.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// Create basic return result
		$returnResult = $this->returnSuccessResult($result);

		$domainnameNode			= $result->query('/epp/response/resData/domain:infData/domain:name');
		//$statusNode				= $result->query('/epp/response/resData/domain:infData/domain:status/@s');
		$registrantNode			= $result->query('/epp/response/resData/domain:infData/domain:registrant');
		$contactsNode			= $result->query('/epp/response/resData/domain:infData/domain:contact');
		$nameserversNode		= $result->query('/epp/response/resData/domain:infData/domain:ns/domain:hostObj');
		$authorisationcodeNode	= $result->query('/epp/response/resData/domain:infData/domain:authInfo/domain:pw');
		$registrant = new SidnContact($registrantNode->item(0)->nodeValue);
		$registrant->setContactType(SidnContact::CONTACT_TYPE_REGISTRANT);
		$domainInfo = new SidnDomainInfo($registrant, $authorisationcodeNode->item(0)->nodeValue);

		for ($i = 0; $i < $nameserversNode->length; $i++)
		{
			$nameserverName	= $nameserversNode->item($i)->nodeValue;
			$nameserver		= new SidnNameserverInfo($nameserverName);
			$domainInfo->addNameserver($nameserver);
		}
		for ($i = 0; $i < $contactsNode->length; $i++)
		{
			$contactNode	= $result->query('/epp/response/resData/domain:infData/domain:contact['.($i+1).']');
			$contact		= new SidnContact($contactNode->item(0)->nodeValue);
			$contact->setContactType($contactNode->item(0)->getAttribute('type'));
			$domainInfo->addContact($contact);
		}

		$domain->setDomainInfo($domainInfo);

		// success
		$returnValue = new SidnDomainInfoResult($returnResult->getStatus(), $returnResult->getCode(),
				$returnResult->getStatusMessage(), $domain);
		return $returnValue;
	}

	/**
	 * Create/register a domain
	 *
	 * @param SidnDomain $domain
	 * @return SidnEppResult
	 */
	public function createDomain(SidnDomain $domain)
	{
		if (!$domain->getDomainInfo() instanceof SidnDomainInfo)
		{
			throw new SidnEppException('Please add the domain info to the domain so we know which contacts to add.');
		}
		$domainInfo = $domain->getDomainInfo();

		if (!$domainInfo->getContactLength())
		{
			throw new SidnEppException('Please add at least 1 registrant, 1 admin, 1 tech contact to this domain.');
		}
		$vars = array();
		$vars['techcontact'] = '';

		// Loop trough the contacts to add to this domain
		for ($i = 0; $i < $domainInfo->getContactLength(); $i++)
		{
			$contact = $domainInfo->getContact($i);
			switch ($contact->getContactType())
			{
				case SidnContact::CONTACT_TYPE_REGISTRANT:
					if (!($vars['registrant']))
					{
						$vars['registrant'] = $contact->getContactHandle();
					}
					else
					{
						throw new SidnEppException('You can only have 1 registrant contact per domain');
					}
					break;
				case SidnContact::CONTACT_TYPE_TECH:
					$vars['techcontact'] .= '<domain:contact type="tech">'.$contact->getContactHandle().'</domain:contact>';
					break;
				case SidnContact::CONTACT_TYPE_ADMIN:
					if (!($vars['admin']))
					{
						$vars['admin'] = $contact->getContactHandle();
					}
					else
					{
						throw new SidnEppException('You can only have 1 admin contact per domain');
					}
					break;
			}
		}
		// Some sanitizing the values
		if (!($vars['registrant']))
		{
			throw new SidnEppException('Please add at least 1 registrant contact to this domain.');
		}
		if (!($vars['admin']))
		{
			throw new SidnEppException('Please add at least 1 admin contact to this domain.');
		}
		if (!($vars['techcontact']))
		{
			throw new SidnEppException('Please add at least 1 tech contact to this domain.');
		}

		//Add the optional nameservers if we have those given.
		$vars['nameservers'] = '';
		for ($i = 0; $i < $domainInfo->getNameserverLength(); $i++)
		{
			$vars['nameservers'] .=  '<domain:hostObj>'.$domainInfo->getNameserver($i)->getHostname().'</domain:hostObj>';
 		}
		//If there are nameservers wrap it in a domain:ns element
		if ($vars['nameservers'])
		{
			$vars['nameservers'] = '<domain:ns>'.$vars['nameservers'].'</domain:ns>';
		}
		// fill template
		$vars['domainname']  = $domain->getDomainName();
		$vars['password']    = substr(md5(date('s') . $domain->getDomainName()),1,8);

		// write command
		$result = $this->writeAndRead('create_domain.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// success
		return $this->returnSuccessResult($result);
	}
	
	/**
	 * This will add a nameserver to a domain
	 *
	 * @param SidnDomain $domain
	 * @return SidnEppResult
	 */
	public function updateDomainAddNameserver(SidnDomain $domain)
	{

		if (!$domain->getDomainInfo() instanceof SidnDomainInfo)
		{
			throw new SidnEppException('Please add the domain info to the domain so we know which nameservers to add.');
		}
		$domainInfo = $domain->getDomainInfo();

		if (!$domainInfo->getNameserverLength())
		{
			throw new SidnEppException('Please add at least 1 nameserver to this domain to add.');
		}

		// fill template
		$vars = array();

		// loop trough nameservers
		for ($i = 0; $i < $domainInfo->getNameserverLength(); $i++)
		{
			$nameserver = $domainInfo->getNameserver($i);
			$vars['nameservers'] .= '<domain:hostObj>'. $nameserver->getHostname(). '</domain:hostObj>';
		}

		$vars['domainname']     = $domain->getDomainName();

		// send command
		$result = $this->writeAndRead('update_domain_add_nameserver.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// success
		return $this->returnSuccessResult($result);
	}

	/**
	 * This will remove a nameserver from a domain
	 *
	 * @param SidnDomain $domain
	 * @return SidnEppResult
	 */
	public function updateDomainRemoveNameserver(SidnDomain $domain)
	{
		// fill template
		$vars = array();
		if (!$domain->getDomainInfo() instanceof SidnDomainInfo)
		{
			throw new SidnEppException('Please add the domain info to the domain so we know which nameservers to remove.');
		}
		$domainInfo = $domain->getDomainInfo();

		if (!$domainInfo->getNameserverLength())
		{
			throw new SidnEppException('Please add at least 1 nameserver to this domain to remove.');
		}

		// Loop trough contacts until we find a registrant contact
		for ($i = 0; $i < $domainInfo->getNameserverLength(); $i++)
		{
			$nameserver = $domainInfo->getNameserver($i);
			$vars['nameservers'] .= '<domain:hostObj>'.$nameserver->getHostname().'</domain:hostObj>';
		}
		$vars['domainname']     = $domain->getDomainName();

		// send command
		$result = $this->writeAndRead('update_domain_remove_nameserver.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// success
		return $this->returnSuccessResult($result);
	}

	/**
	 * This will replace admin & tech contact handles to a domain
	 *
	 * @param SidnDomain $domain
	 * @param SidnDomainInfo $newInfo
	 * @return SidnEppResult
	 */
	public function updateDomainReplaceContacts(SidnDomain $domain, SidnDomainInfo $newInfo)
	{		
		// fill template
		$vars = array();
		if (!$domain->getDomainInfo() instanceof SidnDomainInfo)
		{
			throw new SidnEppException('Please add the domain info to the domain so we know which contacts to add.');
		}
		$domainInfo = $domain->getDomainInfo();

		if (!$domainInfo->getContactLength())
		{
			throw new SidnEppException('Please add at least 1 contact to this domain to remove.');
		}

		if (!$newInfo->getContactLength())
		{
			throw new SidnEppException('Please add at least 1 contact to this domain to add.');
		}
		
		$vars = array();
		$vars['domainname']	= $domain->getDomainName();

		$oldContacts = '';
		// Loop trough old contacts
		for ($i = 0; $i < $domainInfo->getContactLength(); $i++)
		{
			$contact = $domainInfo->getContact($i);
			switch ($contact->getContactType())
			{
				case SidnContact::CONTACT_TYPE_REGISTRANT:
					// do nothing with registrant
					break;
				case SidnContact::CONTACT_TYPE_TECH:
					$oldContacts .= '<domain:contact type="tech">'.$contact->getContactHandle().'</domain:contact>';
					break;
				case SidnContact::CONTACT_TYPE_ADMIN:
					$oldContacts .= '<domain:contact type="admin">'.$contact->getContactHandle().'</domain:contact>';
					break;
			}
		}
		
		if ($oldContacts)
		{
			$vars['oldContacts'] = '<domain:rem>' . $oldContacts . '</domain:rem>';
		}
		
		// Loop trough new contacts
		for ($i = 0; $i < $newInfo->getContactLength(); $i++)
		{
			$contact = $newInfo->getContact($i);
			switch ($contact->getContactType())
			{
				case SidnContact::CONTACT_TYPE_REGISTRANT:
					// do nothing with registrant
					break;
				case SidnContact::CONTACT_TYPE_TECH:
					$newContacts .= '<domain:contact type="tech">'.$contact->getContactHandle().'</domain:contact>';
					break;
				case SidnContact::CONTACT_TYPE_ADMIN:
					$newContacts .= '<domain:contact type="admin">'.$contact->getContactHandle().'</domain:contact>';
					break;
			}
		}
		
		if ($newContacts)
		{
			$vars['newContacts'] = '<domain:add>' . $newContacts . '</domain:add>';
		}

		if (!$newContacts && !$oldContacts)
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'could not replace contact handles: no new or old contacts received');
		}

		// send command
		$result = $this->writeAndRead('update_domain_replace_contacts.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// success
		return $this->returnSuccessResult($result);
	}

	/**
	 * This will add contact handles to a domain
	 *
	 * @param SidnDomain $domain
	 * @return SidnEppResult
	 */
	public function updateDomainAddContact(SidnDomain $domain)
	{
		// fill template
		$vars = array();
		if (!$domain->getDomainInfo() instanceof SidnDomainInfo)
		{
			throw new SidnEppException('Please add the domain info to the domain so we know which contacts to add.');
		}
		$domainInfo = $domain->getDomainInfo();

		if (!$domainInfo->getContactLength())
		{
			throw new SidnEppException('Please add at least 1 contact to this domain to add.');
		}
		$vars = array();
		//Loop trough contacts
		for ($i = 0; $i < $domainInfo->getContactLength(); $i++)
		{
			$contact = $domainInfo->getContact($i);
			switch ($contact->getContactType())
			{
				case SidnContact::CONTACT_TYPE_REGISTRANT:
					throw new SidnEppException('You can\'t add a registrant contact!');
					break;
				case SidnContact::CONTACT_TYPE_TECH:
					$vars['contacts'] .= '<domain:contact type="tech">'.$contact->getContactHandle().'</domain:contact>';
					break;
				case SidnContact::CONTACT_TYPE_ADMIN:
					$vars['contacts'] .= '<domain:contact type="admin">'.$contact->getContactHandle().'</domain:contact>';
					break;
			}
		}
		$vars['domainname']     = $domain->getDomainName();
		// send command
		$result = $this->writeAndRead('update_domain_add_contact.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// success
		return $this->returnSuccessResult($result);
	}

	/**
	 * This will remove contact handles from a domain
	 *
	 * @param SidnDomain $domain
	 * @return SidnEppResult
	 */
	public function updateDomainRemoveContact(SidnDomain $domain)
	{

		// fill template
		$vars = array();
		if (!$domain->getDomainInfo() instanceof SidnDomainInfo)
		{
			throw new SidnEppException('Please add the domain info to the domain so we know which contacts to remove.');
		}
		$domainInfo = $domain->getDomainInfo();

		if (!$domainInfo->getContactLength())
		{
			throw new SidnEppException('Please add at least 1 contact to this domain for removal.');
		}
		$vars = array();
		//Loop trough contacts
		for ($i = 0; $i < $domainInfo->getContactLength(); $i++)
		{
			$contact = $domainInfo->getContact($i);
			switch ($contact->getContactType())
			{
				case SidnContact::CONTACT_TYPE_REGISTRANT:
					throw new SidnEppException('You can\'t remove a registrant contact!');
					break;
				case SidnContact::CONTACT_TYPE_TECH:
					$vars['contacts'] .= '<domain:contact type="tech">'.$contact->getContactHandle().'</domain:contact>';
					break;
				case SidnContact::CONTACT_TYPE_ADMIN:
					$vars['contacts'] .= '<domain:contact type="admin">'.$contact->getContactHandle().'</domain:contact>';
					break;
			}
		}
		$vars['domainname']     = $domain->getDomainName();


		// send command
		$result = $this->writeAndRead('update_domain_remove_contact.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// success
		return $this->returnSuccessResult($result);

	}

	/**
	 * Change the registrant contact of the domainname
	 *
	 * @param SidnDomain $domain
	 * @return SidnEppResult
	 */
	public function updateDomainChangeRegistrant(SidnDomain $domain)
	{
		if (!$domain->getDomainInfo() instanceof SidnDomainInfo)
		{
			throw new SidnEppException('Please add the domain info to the domain so we know which contacts to add.');
		}
		$domainInfo = $domain->getDomainInfo();

		if (!$domainInfo->getContactLength())
		{
			throw new SidnEppException('Please add at least 1 registrant to this domain.');
		}
		$vars = array();

		//Loop trough contacts until we find a registrant contact
		for ($i = 0; $i < $domainInfo->getContactLength(); $i++)
		{
			$contact = $domainInfo->getContact($i);
			switch ($contact->getContactType())
			{
				case SidnContact::CONTACT_TYPE_REGISTRANT:
					if (!$vars['registrant'])
					{
						$vars['registrant'] = $contact->getContactHandle();
					}
					else
					{
						throw new SidnEppException('You can only have 1 registrant contact per domain');
					}
					break;
			}
		}

		if (!$vars['registrant'])
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'Could not update registrant handle for domain ' . $domain->getDomainName() . ': No replacement handle given');
		}
		$vars['domainname']     = $domain->getDomainName();


		// send command
		$result = $this->writeAndRead('update_domain_change_registrant.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}
		
		// success
		return $this->returnSuccessResult($result);
	}

	/**
	 * This will delete the given domainname at SIDN
	 *
	 * @param SidnDomain $domain
	 * @return SidnEppResult
	 */
	public function deleteDomain(SidnDomain $domain)
	{

		// fill template
		$vars = array();
		$vars['domainname'] = $domain->getDomainName();

		// send command
		$result = $this->writeAndRead('delete_domain.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// success
		return $this->returnSuccessResult($result);
	}

	/**
	 * This will cancel a requested domain delete or otherwise explained:
	 * This will unquarantine the given domainname
	 *
	 * @param string $domain
	 * @return SidnEppResult
	 */
	public function cancelDeleteDomain(SidnDomain $domain)
	{

		// fill template
		$vars = array();
		$vars['domainname'] = $domain->getDomainName();

		// send command
		$result = $this->writeAndRead('cancel_delete_domain.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// success
		return $this->returnSuccessResult($result);
	}

	/**
	 * This will query the status of a domain transfer
	 * @param string $domainname domain transfer to query
	 * @return SidnTransferInfoResult
	 */
	public function queryDomainTransfer(SidnDomain $domain)
	{
		// sanity
		if (!$domain)
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'missing argument(s)');
		}

		// fill template
		$vars = array();
		$vars['domainname'] = $domain->getDomainName();
		$vars['operation']  = 'query';

		// send command
		$result = $this->writeAndRead('transfer_domain.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// read response
		$transferStatus		 = $result->query('/epp/response/resData/domain:trnData/domain:trStatus');
		$requestingRegistrar = $result->query('/epp/response/resData/domain:trnData/domain:reID');
		$sendingRegistrar	 = $result->query('/epp/response/resData/domain:trnData/domain:acID');

		$info = new SidnTransferInfo($domain->getDomainName());
		
		if ($transferStatus instanceof DOMNodeList)
		{
			$info->setTransferStatus($transferStatus->item(0)->nodeValue);
		}
		if ($requestingRegistrar instanceof DOMNodeList)
		{
			$info->setRequestingRegistrar($requestingRegistrar->item(0)->nodeValue);
		}

		if ($sendingRegistrar instanceof DOMNodeList)
		{
			$info->setSendingRegistrar($sendingRegistrar->item(0)->nodeValue);
		}

		// success
		$returnValue = new SidnTransferInfoResult(SidnEppResult::SUCCESS);
		$returnValue->setInfo($info);
		
		return $returnValue;
	}
	
	/**
	 * This will request the domain transfer for the given domainname
	 *
	 * @param string $domainname domainname to tranfer
	 * @param string $authorisationCode authorisation code to use with the transfer
	 * @return SidnEppResult
	 */
	public function requestDomainTransfer(SidnDomain $domain)
	{
		// sanity
		if (!$domain)
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'missing argument(s)');
		}
		$info = $domain->getDomainInfo();

		if ($info)
		{
			$authCode = $info->getAuthorisationCode();
			if (!$authCode || $authCode == '')
			{
				return new SidnEppResult(SidnEppResult::ERROR, 'cannot request transfer: missing authorisation code');
			}
		}
		
		// fill template
		$vars = array();
		$vars['domainname'] = $domain->getDomainName();
		$vars['operation']  = 'request';
		$vars['pwblock']    = '<domain:authInfo><domain:pw>' . $info->getAuthorisationCode() . '</domain:pw></domain:authInfo>';

		// send command
		$result = $this->writeAndRead('transfer_domain.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult)
		{
			return $result;
		}
		else if (!($result instanceof DOMXPath))
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'unexpected result while requesting domain transfer');
		}

		// success
		return new SidnEppResult(SidnEppResult::SUCCESS);
	}

	/**
	 * This will cancel the domain tranfer for the given domainname
	 *
	 * @param string $domainname domainname to cancel tranfer of
	 * @return SidnEppResult
	 */
	public function cancelDomainTransfer(SidnDomain $domain)
	{
		// sanity
		if (!$domain)
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'missing argument(s)');
		}

		// fill template
		$vars = array();
		$vars['domainname'] = $domain->getDomainName();
		$vars['operation']  = 'cancel';

		// send command
		$result = $this->writeAndRead('transfer_domain.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// success
		return new SidnEppResult(SidnEppResult::SUCCESS);
	}

	/**
	 * This will approve the transfer of a domainname
	 * @param string $domainname domainname to approve transfer for
	 * @return SidnEppResult
	 */
	public function approveDomainTransfer(SidnDomain $domain)
	{
		// sanity
		if (!$domain)
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'missing argument(s)');
		}

		// fill template
		$vars = array();
		$vars['domainname'] = $domain->getDomainName();
		$vars['operation']  = 'approve';

		// send command
		$result = $this->writeAndRead('transfer_domain.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult)
		{
			return $result;
		}
		else if (!($result instanceof DOMXPath))
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'unexpected result wile approving domaing transferrr');
		}

		// success
		return new SidnEppResult(SidnEppResult::SUCCESS);
	}

	/**
	 * get the next message from the response messages stack of SIDN
	 *
	 * @return in failure: SidnEppResult
	 *		   in success: SidnResponseMessage
	 */
	public function poll()
	{
		// fill template
		$vars = array();
		$vars['operation'] = 'req';

		// send command
		$result = $this->writeAndRead('poll.xml', $vars, self::EPP_COMMAND_POLLED_OK);

		// fail?
		if ($result instanceof SidnEppResult)
		{
			return $result;
		}
		else if (!($result instanceof DOMXPath))
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'unexpected result while polling responses');
		}

		// read response
		$message		= $result->query('/epp/response/result/msg');
		$messageId		= $result->query('/epp/response/msgQ/@id');
		$queueLength	= $result->query('/epp/response/msgQ/@count');
		$command		= $result->query('/epp/response/resData/sidn-ext-epp:pollData/sidn-ext-epp:command');
		$commandCode	= $result->query('/epp/response/resData/sidn-ext-epp:pollData/sidn-ext-epp:data/result/@code');
		$commandMessage	= $result->query('/epp/response/resData/sidn-ext-epp:pollData/sidn-ext-epp:data/result/msg');
		$domainName		= $result->query('/epp/response/resData/sidn-ext-epp:pollData/sidn-ext-epp:data/resData/domain:trnData/domain:name');
		$domainStatus	= $result->query('/epp/response/resData/sidn-ext-epp:pollData/sidn-ext-epp:data/resData/domain:trnData/domain:trStatus');
		$authCode		= $result->query('/epp/response/resData/sidn-ext-epp:pollData/sidn-ext-epp:data/extension/sidn-ext-epp:ext/sidn-ext-epp:trnData/sidn-ext-epp:pw');

		// fill and return result object
		$return			= new SidnResponseMessage(SidnEppResult::SUCCESS, $message->item(0)->nodeValue);

		if ($messageId instanceof DOMNodeList)
		{
			$return->setId($messageId->item(0)->nodeValue);
		}
		if ($queueLength instanceof DOMNodeList)
		{
			$return->setQueueLength($queueLength->item(0)->nodeValue);
		}
		if ($command instanceof DOMNodeList)
		{
			$return->setResponseCommand($command->item(0)->nodeValue);
		}
		if ($commandCode instanceof DOMNodeList)
		{
			$return->setResponseCode($commandCode->item(0)->nodeValue);
		}
		if ($commandMessage instanceof DOMNodeList)
		{
			$return->setResponseMessage($commandMessage->item(0)->nodeValue);
		}
		if ($domainName instanceof DOMNodeList)
		{
			$return->setResponseDomainname($domainName->item(0)->nodeValue);
		}
		if ($domainStatus instanceof DOMNodeList)
		{
			$return->setResponseDomainStatus($domainStatus->item(0)->nodeValue);
		}
		if ($authCode instanceof DOMNodeList)
		{
			$return->setResponseAuthcode($authCode->item(0)->nodeValue);
		}
		return $return;
	}

	/**
	 * Confirm receiving and processing a SIDN response message
	 *
	 * @param <integer> $messageId
	 * @return SidnEppResult
	 */
	public function confirmResponse(SidnResponseMessage $message)
	{
		if (!$message)
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'missing argument: message');
		}

		// fill template
		$vars				= array();
		$vars['operation']	= 'ack';
		$vars['msgID']		= 'msgID="' . $message->getId() . '"';

		// send command
		$result = $this->writeAndRead('poll.xml', $vars);

		// interprete and return result
		if ($result instanceof SidnEppResult)
		{
			return $result;
		}
		else if ($result instanceof DOMXPath)
		{
			return new SidnEppResult(SidnEppResult::SUCCESS);
		}
		else
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'unexpected result while confirming received response message');
		}
	}

	/**
	 * This will check contact handle availability
	 *
	 * @param SidnContact $contact
	 * @return SidnEppResult
	 */
	public function checkContact(SidnContact $contact)
	{

		// fill template
		$vars = array();
		$vars['contactHandle'] = $contact->getContactHandle();

		// send command
		$result = $this->writeAndRead('check_contact.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// read response and return
		$contactAvailable  = $result->query('/epp/response/resData/contact:chkData/contact:cd/contact:id/@avail');

		if (!($contactAvailable instanceof DOMNodeList))
		{
			return new SidnEppResult(SidnEppResult::ERROR,'could not determine status of contact');
		}

		if ($contactAvailable->item(0)->nodeValue == 'true')
		{
			return new SidnEppResult(SidnEppResult::CONTACT_AVAILABLE);
		}
		else if ($contactAvailable->item(0)->nodeValue == 'false')
		{
			return new SidnEppResult(SidnEppResult::CONTACT_UNAVAILABLE);
		}
		else
		{
			return new SidnEppResult(SidnEppResult::ERROR,'could not determine status of contact');
		}
	}

	/**
	 * This will check availability of a domain
	 * @param SidnDomain $domainname
	 * @return SidnEppResult
	 */
	public function checkDomainAvailability(SidnDomain $domain)
	{
		// sanity check
		if ($this->connectionState != self::EPP_LOGGED_IN || !$domain)
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'You are not logged in');
		}

		// fill template
		$vars = array();
		$vars['domainname'] = $domain->getDomainName();

		// send command
		$result = $this->writeAndRead('check_domain.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// read response and return
		$domainAvailable = $result->query('/epp/response/resData/domain:chkData/domain:cd/domain:name/@avail');

		if ($domainAvailable->item(0)->nodeValue == 'true')
		{
			return new SidnEppResult(SidnEppResult::DOMAIN_AVAILABLE);
		}
		else
		{
			return new SidnEppResult(SidnEppResult::DOMAIN_UNAVAILABLE);
		}
	}

	/**
	 * This will log ug out from the SIDN EPP server
	 */
	private function doLogout()
	{
		// fill template
		$vars = array();

		// send command
		$xml = SidnTools::getXml('logout.xml', $vars);
		if (!$this->write($xml))
		{
			return new SidnEppResult(SidnEppResult::ERROR);
		}

		// read response
		$response       = $this->parseXml($this->read());
		$responseXPath  = new DOMXPath($response);
		$code           = $responseXPath->query('/epp/response/result/@code');

		// fail?
		if ($code->item(0)->nodeValue != self::EPP_COMMAND_LOGOUT_OK)
		{
			return new SidnEppResult(SidnEppResult::ERROR);
		}

		// success
		$this->connectionState = self::EPP_CONNECTED;
		return new SidnEppResult(SidnEppResult::SUCCESS, 'logout successful');
	}

	/**
	 * This will retrieve info of a contact handle
	 *
	 * @param SidnContact $contactHandle
	 * @return SidnContactInfoResult
	 */
	public function getContactInfo(SidnContact $contact)
	{
		// fill template
		$vars = array();
		$vars['contactHandle'] = $contact->getContactHandle();

		// send command
		$result = $this->writeAndRead('info_contact.xml', $vars);
		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// Retrieve base nodes for basic info
		$contactHandleNode		= $result->query('/epp/response/resData/contact:infData/contact:id');
		$postalInfoNodes		= $result->query('/epp/response/resData/contact:infData/contact:postalInfo');
		$voiceNode				= $result->query('/epp/response/resData/contact:infData/contact:voice');
		$faxNode				= $result->query('/epp/response/resData/contact:infData/contact:fax');
		$emailNode				= $result->query('/epp/response/resData/contact:infData/contact:email');
		$legalFormNode			= $result->query('/epp/response/extension/sidn-ext-epp:ext/sidn-ext-epp:infData/sidn-ext-epp:contact/sidn-ext-epp:legalForm');

		//Create basic return result
		$returnResult = $this->returnSuccessResult($result);

		if ($contactHandleNode instanceof DOMNodeList)
		{
			$contactHandle  = $contactHandleNode->item(0)->nodeValue;
		}
		if ($postalInfoNodes instanceof DOMNodeList)
		{
			//We can have multiple of those...
			for ($i = 0; $i < $postalInfoNodes->length; $i++)
			{
				//Retrieve the address information nodes
				$nameNode				= $result->query('/epp/response/resData/contact:infData/contact:postalInfo['.($i+1).']/contact:name');
				$organisationNameNode	= $result->query('/epp/response/resData/contact:infData/contact:postalInfo['.($i+1).']/contact:org');
				$streetNodes			= $result->query('/epp/response/resData/contact:infData/contact:postalInfo['.($i+1).']/contact:addr/contact:street');
				$cityNode				= $result->query('/epp/response/resData/contact:infData/contact:postalInfo['.($i+1).']/contact:addr/contact:city');
				$provinceNode			= $result->query('/epp/response/resData/contact:infData/contact:postalInfo['.($i+1).']/contact:addr/contact:sp');
				$zipcodeNode			= $result->query('/epp/response/resData/contact:infData/contact:postalInfo['.($i+1).']/contact:addr/contact:pc');
				$countrycodeNode		= $result->query('/epp/response/resData/contact:infData/contact:postalInfo['.($i+1).']/contact:addr/contact:cc');
				if ($nameNode instanceof DOMNodeList)
				{
					$name = $nameNode->item(0)->nodeValue;
				}
				if ($organisationNameNode instanceof DOMNodeList)
				{
					$organisationName = $organisationNameNode->item(0)->nodeValue;
				}
				unset($streets);
				//It's possible to have 3 streets
				if ($streetNodes instanceof DOMNodeList)
				{
					for ($a = 0; $a < $streetNodes->length; $a++)
					{
						$streets[] = $streetNodes->item($a)->nodeValue;
					}
				}
				#var_dump($cityNode->length);
				if ($cityNode instanceof DOMNodeList)
				{
					$city = $cityNode->item(0)->nodeValue;
				}
				if ($provinceNode instanceof DOMNodeList)
				{
					$province = $provinceNode->item(0)->nodeValue;
				}
				if ($zipcodeNode instanceof DOMNodeList)
				{
					$zipcode = $zipcodeNode->item(0)->nodeValue;
				}
				if ($countrycodeNode instanceof DOMNodeList)
				{
					$countrycode = $countrycodeNode->item(0)->nodeValue;
				}
				$postalInfo = new SidnContactPostalInfo($name, $city, $countrycode);

				$postalInfo->setOrganisationName($organisationName);
				foreach ($streets as $street)
				{
					$postalInfo->addStreet($street);
				}

				$postalInfo->setProvince($province);
				$postalInfo->setZipcode($zipcode);
				//Store it for now in a array which will be looped later on
				$postalInfos[] = $postalInfo;
			}
		}

		if ($voiceNode instanceof DOMNodeList)
		{
			$voice  = $voiceNode->item(0)->nodeValue;
		}
		if ($faxNode instanceof DOMNodeList)
		{
			$fax = $faxNode->item(0)->nodeValue;
		}
		if ($emailNode instanceof DOMNodeList)
		{
			$email  = $emailNode->item(0)->nodeValue;
		}
		if ($legalFormNode instanceof DOMNodeList)
		{
			$legalForm  = $legalFormNode->item(0)->nodeValue;
		}
		$contactInfo = new SidnContactInfo($postalInfos[0], $email, $legalForm, null, $voice, $fax);
		// Do we have more postal info available?, Add those to the contact info.
		if (count($postalInfos) > 1)
		{
			//Start at 1 because we already added the first one
			for ($i = 1; $i < count($postalInfos); $i++)
			{
				$contactInfo->addPostalInfo($postalInfos[$i]);
			}
		}
		$contact->setContactInfo($contactInfo);
		$contactInfoResult = new SidnContactInfoResult($returnResult->getStatus(), $returnResult->getCode(),
				$returnResult->getStatusMessage(), $contact);

		// success
		return $contactInfoResult;
	}


	/**
	 * Creates a contact over at SIDN
	 *
	 * @param SidnContactInfo $contactInfo
	 * @return SidnCreateContactResult
	 */
	public function createContact(SidnContactInfo $contactInfo)
	{
		$vars['contactInfo']	= $contactInfo->toXml();
		$vars['contactHandle']  = 'none'; //We should at least give SIDN something, according to the docs
		$vars['legalForm']		= $contactInfo->getLegalForm();

		// send command
		$result = $this->writeAndRead('create_contact.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult)
		{
			return new SidnCreateContactResult($result->getStatus(), $result->getCode(), $result->getStatusMessage());
		}

		// read response
		$contactHandleNode  = $result->query('/epp/response/resData/contact:creData/contact:id');
		if (!$contactHandleNode)
		{
			return new SidnCreateContactResult(SidnEppResult::ERROR, 0, 'create_contact returned success, but no contactid');
		}

		// success
		$return = $this->returnSuccessResult($result);
		$contact = new SidnContact($contactHandleNode->item(0)->nodeValue, $contactInfo);
		return new SidnCreateContactResult($return->getStatus(), $return->getCode(), $return->getStatusMessage(), $contact);
	}

	/**
	 * Update the contacthandle's information
	 *
	 * @param SidnContact $contact
	 * @return SidnEppResult
	 */
	public function updateContact(SidnContact $contact)
	{
		$vars['contactHandle']	= $contact->getContactHandle();
		$vars['contactInfo']	= $contact->getContactInfo()->toXml(true);
		
		// send command
		$result = $this->writeAndRead('update_contact.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// Success
		return new SidnEppResult(SidnEppResult::SUCCESS);
	}

	/**
	 * Check nameserver availability
	 * @param SidnNameserverInfo $info
	 * @todo support multiple IP addresses
	 * @return SidnEppResult
	 */
	public function checkNameserver(SidnNameserverInfo $info)
	{
		// sanity
		if (!$info)
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'missing argument(s)');
		}

		// fill template
		$vars = array();
		$vars['hostname']   = $info->getHostname();

		// send command
		$result = $this->writeAndRead('check_nameserver.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// read response and return
		$nameserverAvailable  = $result->query('/epp/response/resData/host:chkData/host:cd/host:name/@avail');

		if ($nameserverAvailable->item(0)->nodeValue == 'true')
		{
			return new SidnEppResult(SidnEppResult::NAMESERVER_AVAILABLE);
		}
		else if ($nameserverAvailable->item(0)->nodeValue == 'false')
		{
			return new SidnEppResult(SidnEppResult::NAMESERVER_UNAVAILABLE);
		}
		else
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'could not determine nameserver availability');
		}
	}

	/**
	 * This will retrieve info of a nameserver
	 *
	 * @param string $nameserver Hostname of nameserver
	 * @todo support multiple IP addresses
	 * @return SidnNameserverInfoResult
	 */
	public function getNameserverInfo(SidnNameserverInfo $info)
	{
		// sanity
		if (!$info)
		{
			return new SidnNameserverInfoResult(SidnEppResult::ERROR, null, 'missing argument(s)');
		}

		// fill template
		$vars = array();
		$vars['hostname'] = $info->getHostname();

		// send command
		$result = $this->writeAndRead('info_nameserver.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return new SidnNameserverInfoResult($result->getStatus(), $result->getCode(), $result->getStatusMessage());
		}

		// read response
		$hostname			= $result->query('/epp/response/resData/host:infData/host:name');
		$ipaddress			= $result->query('/epp/response/resData/host:infData/host:addr');
		$nameserverStatus	= $result->query('/epp/response/resData/host:infData/host:status/@s');

		if (!($hostname instanceof DOMNodeList))
		{
			return new SidnNameserverInfoResult(SidnEppResult::ERROR, $result->getCode(), 'could not process info nameserver results');
		}

		$data = new SidnNameserverInfo($hostname->item(0)->nodeValue);

		if ($ipaddress instanceof DOMNodeList)
		{
			$data->setIpaddress($ipaddress->item(0)->nodeValue);
		}
		if ($nameserverStatus instanceof DOMNodeList)
		{
			$data->setNameserverStatus($nameserverStatus->item(0)->nodeValue);
		}

		// success
		$returnValue = new SidnNameserverInfoResult(SidnEppResult::SUCCESS);
		$returnValue->setInfo($data);
		return $returnValue;
	}

	/**
	 * Create a nameserver with a given hostname
	 *
	 * @param SidnNameserverInfo $info
	 * @todo support multiple IP addresses
	 * @return SidnEppResult
	 */
	public function createNameserver(SidnNameserverInfo $info)
	{
		// sanity
		if (!$info)
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'missing argument: hostname');
		}

		// fill template
		$vars = array();
		$vars['hostname']   = $info->getHostname();
		if ($info->getIpaddress() && $info->getIpaddress() != '')
		{
			$vars['ipaddress']  = '<host:addr ip="' . $info->GetIpAddressVersion() . '">' . $info->getIpaddress() . '</host:addr>';
		}

		// send command
		$result = $this->writeAndRead('create_nameserver.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// success
		$return = $this->returnSuccessResult($result);
		return $return;
	}

	/**
	 * Add Ip address to a nameserver
	 *
	 * @param string $nameserver
	 * @param string $IpAddress
	 * @todo support multiple IP addresses
	 * @return SidnEppResult
	 */
	public function updateNameserverAddIpAddress(SidnNameserverInfo $info)
	{
		// sanity
		if (!$info)
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'could not add ipaddress to nameserver: invalid arguments');
		}

		if (!$info->getHostname())
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'could not add ipaddress to nameserver: invalid hostname');
		}

		if (!$info->getIpaddress())
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'could not add ipaddress to nameserver: invalid IP address given');
		}
		
		// fill template
		$vars = array();
		$vars['iptype']     = $info->getIpaddressVersion();
		$vars['hostname']   = $info->getHostname();
		$vars['ipaddress']  = $info->getIpaddress();

		// send command
		$result = $this->writeAndRead('update_nameserver_add_ipaddress.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// success
		return new SidnEppResult(SidnEppResult::SUCCESS);
	}

	/**
	 * update nameserver with new IP address (and remove the old one)
	 *
	 * @param SidnNameserverInfo $info
	 * @todo support multiple IP addresses
	 * @return SidnEppResult
	 */
	public function changeNameserverIpaddress(SidnNameserverInfo $info)
	{
		// sanity
		if (!($info))
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'could not change ipaddress of nameserver: invalid arguments');
		}

		// get info to retrieve ip address
		$infoResult = $this->getNameserverInfo($info);

		// fail?
		if (!($infoResult instanceof SidnNameserverInfoResult))
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'unexpected result while info nameserver ' . $info->getHostnam());
		}
		if ($infoResult->getStatus() != SidnEppResult::SUCCESS)
		{
			return new SidnEppResult($infoResult->getStatus(), $infoResult->getStatusMessage());
		}

		$oldData = $infoResult->getInfo();
		$oldIp   = $oldData->getIpaddress();

		// remove old ip if it existed
		if ($oldIp && $oldIp != '')
		{
			$removeResult = $this->updateNameserverRemoveIpAddress($oldData);

			// fail?
			if ($removeResult->getStatus() != SidnEppResult::SUCCESS)
			{
				return $removeResult;
			}
		}

		// add new ip
		$addResult = $this->updateNameserverAddIpAddress($info);

		// fail?
		if ($addResult->getStatus() != SidnEppResult::SUCCESS)
		{
			return $addResult;
		}

		// success
		return new SidnEppResult(SidnEppResult::SUCCESS);
	}

	/**
	 * Remove an IPp address from a nameserver
	 *
	 * @param SidnNameserverInfo $info
	 * @todo support multiple IP addresses
	 * @return SidnEppResult
	 */
	public function updateNameserverRemoveIpAddress(SidnNameserverInfo $info)
	{
		// sanity
		if (!$info)
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'could not remove ipaddress from nameserver: invalid arguments');
		}
		// TODO: check hostname and ip are both filled

		// fill template
		$vars = array();
		$vars['iptype']     = $info->getIpaddressVersion();
		$vars['hostname']   = $info->getHostname();
		$vars['ipaddress']  = $info->getIpaddress();

		// send command
		$result = $this->writeAndRead('update_nameserver_remove_ipaddress.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// success
		return new SidnEppResult(SidnEppResult::SUCCESS);
	}

	/**
	 * This will delete the given nameserver
	 *
	 * @param string $nameserver name of nameserver
	 * @return SidnEppResult
	 */
	public function deleteNameserver(SidnNameserverInfo $info)
	{
		// sanity
		if (!$info)
		{
			return new SidnEppResult(SidnEppResult::ERROR, 'could not delete nameserver: missing argument');
		}

		// fill template
		$vars = array();
		$vars['hostname']   = $info->getHostname();

		// send command
		$result = $this->writeAndRead('delete_nameserver.xml', $vars);

		// fail?
		if ($result instanceof SidnEppResult && $result->getStatus() != SidnEppResult::SUCCESS)
		{
			return $result;
		}

		// success
		return new SidnEppResult(SidnEppResult::SUCCESS);
	}

	/**
	 * This is a diagnostic function that will test wether the epp client is
	 * fully functional and connects to the SIDN interface.
	 * A summary will be send to the standard output.
	 *
	 * If all tests are reported as 'OK', it means the	epp client was properly installed
	 * and is ready for use.
	 */
	public function test()
	{
		// test required classes
		$classResult		= $this->testClasses();

		if ($classResult['errorFound'] == 'true')
		{
			// output
			$this->displayTests($classResult, null, null);
			return false;
		}

		// test required constants
		$constantsResult	= $this->testConstants();

		if ($constantsResult['errorFound'] == 'true')
		{
			$this->displayTests($classResult, $constantsResult, null);
			return false;
		}

		// test SIDN connectivity
		$connectionResult	= $this->testConnection();

		if ($connectionResult['errorFound'] == 'true')
		{
			$returnValue = true;
		}
		else
		{
			$returnValue = false;
		}

		// output
		$this->displayTests($classResult, $constantsResult, $connectionResult);
		return $returnValue;
	}

	// PRIVATE FUNCTIONS

	/**
	 * This will parse the XML returned from the EPP protocol so it's readable for DOMDocument/XPath
	 *
	 * @param string $xml
	 * @return string
	 */
	private function parseXml($xml)
	{
		//Replace the xmlns="" so we can load the XML for use with Xpathing
		$xml = preg_replace('/<epp(\s*xmlns=".*?")?(.*?)>/im', '<epp\\2>', $xml);
		$dom = new DOMDocument();
		$dom->formatOutput = true;

		if ($xml != '')
		{
			$dom->loadXML($xml);
		}
		#echo "----- READ ------\n";
		#echo $dom->saveXML($dom->firstChild)."\n";
		return $dom;
	}

	/**
	 * Perform default operations for writing a request and reading its response
	 *
	 * @param string $template: name of the xml template
	 * @param array $vars: array with template variables
	 * @param string $expectedCode: code that
	 * @return in case of fail	 : SidnEppResult
	 *		   in case of success: DOMNodeList
	 */
	private function writeAndRead($template, $vars, $expectedCode = self::EPP_COMMAND_SUCCESSFUL)
	{
		// send check_domain request
		$xml = SidnTools::getXml($template, $vars);
		//echo "----- WRITE -----\n";
		//echo $xml."\n";

		if( $xml == false)
		{
			throw new Exception ( 'Invalid XML template given. Given xml: ' . $template);
		}
		
		if (!$this->write($xml))
		{
			return new SidnEppResult( SidnEppResult::ERROR, 'Writing to the SIDN server has failed');
		}

		// read response
		$response       = $this->parseXml($this->read());
		$responseXPath  = new DOMXPath($response);
		$code           = $responseXPath->query('/epp/response/result/@code')->item(0)->nodeValue;

		// define non default namespaces before querying it
		$responseXPath->registerNameSpace('contact','urn:ietf:params:xml:ns:contact-1.0');
		$responseXPath->registerNameSpace('domain','urn:ietf:params:xml:ns:domain-1.0');
		$responseXPath->registerNameSpace('host','urn:ietf:params:xml:ns:host-1.0');
		$responseXPath->registerNameSpace('sidn-ext-epp','http://rxsd.domain-registry.nl/sidn-ext-epp-1.0');

		//echo "----- READ -----\n";
		//print_r($response->saveXML());
		
		// was command succesful?
		if ($code != $expectedCode)
		{
			// command failed
			return $this->returnErrorResult($responseXPath);
		}

		// success
		return $responseXPath;
	}

	/**
	 *  Relays a descriptive error result object
	 *
	 * @param <DOMDocument> $responseXPath
	 * @return <SidnEppResult>
	 */
	private function returnErrorResult($responseXPath)
	{
		$code           = $responseXPath->query('/epp/response/result/@code')->item(0)->nodeValue;
		$message		= $responseXPath->query('/epp/response/result/msg')->item(0)->nodeValue;
		if ($code != self::EPP_COMMAND_SYNTAX_ERROR)
		{
			$extMessage = '';
			$extCode	= '';
			$extField	= '';
			$extMessageNode	= $responseXPath->query('/epp/response/extension/sidn-ext-epp:ext/sidn-ext-epp:response/sidn-ext-epp:msg');
			if ($extMessageNode instanceof DOMNodeList)
			{
				$extMessage = $extMessageNode->item(0)->nodeValue;
			}
			$extCodeNode	= $responseXPath->query('/epp/response/extension/sidn-ext-epp:ext/sidn-ext-epp:response/sidn-ext-epp:msg/@code');
			if ($extCodeNode instanceof DOMNodeList)
			{
				$extCode	= $extCodeNode->item(0)->nodeValue;
			}
			$extFieldNode	= $responseXPath->query('/epp/response/extension/sidn-ext-epp:ext/sidn-ext-epp:response/sidn-ext-epp:msg/@field');
			if ($extFieldNode instanceof DOMNodeList)
			{
				$extField	= $extFieldNode->item(0)->nodeValue;
			}

			$result = new SidnEppResult(SidnEppResult::ERROR, $message.', Field: '.$extField.', SIDN Code: '.$extCode.', SIDN Message: '.$extMessage);
		}
		else
		{
			$result = new SidnEppResult(SidnEppResult::ERROR, $message);


		}
		$result->setCode($code);
		return $result;
	}

	/**
	 *  Relays a succesful result oject
	 *
	 * @param <DOMDocument> $responseXPath
	 * @return <SidnEppResult>
	 */
	private function returnSuccessResult($responseXPath)
	{
		$code           = $responseXPath->query('/epp/response/result/@code')->item(0)->nodeValue;
		$message		= $responseXPath->query('/epp/response/result/msg')->item(0)->nodeValue;
		$result = new SidnEppResult(SidnEppResult::SUCCESS, $message);
		$result->setCode($code);
		return $result;
	}

	/**
	 * This will send a hello to SIDN, Hi SIDN, it's me!!
	 */
	private function doHello()
	{
		$xml = SidnTools::getXml('hello.xml', array());
		if ($this->write($xml))
		{
			$this->connectionState = self::EPP_HELLO_SENT;
			return true;
		}
		else
		{
			return false;
		}
	}
	
	/**
	 * This will connect to the SIDN EPP server
	 */
	private function doConnect()
	{
		// Try connecting if needed
		if ($this->connectionState == self::EPP_NOT_CONNECTED)
		{
			// Setup the epp connection
			if ($this->connect(SidnEppConfig::PROTOCOL_PREFIX . SidnEppConfig::SERVER_ADDRESS, SidnEppConfig::SERVER_PORT, SidnEppConfig::TIMEOUT))
			{
				// read response to clear stream buffer
				$this->read();
				
				$this->connectionState = self::EPP_CONNECTED;
				return true;
			}
			else
			{
				return false;
			}
		}
		elseif ($this->connectionState >= self::EPP_CONNECTED)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * This will log you out if needed and close the connection to the SIDN EPP server
	 */
	private function doDisconnect()
	{
		if ($this->connectionState >= self::EPP_CONNECTED)
		{
			if ($this->connectionState >= self::EPP_LOGGED_IN)
			{
				$this->doLogout();
			}
			$this->disconnect();
		}

		//Change state to not connected
		$this->connectionState = self::EPP_NOT_CONNECTED;

		return true;
	}

	/**
	 * This will login us in to SIDN
	 */
	private function doLogin()
	{
		if ($this->connectionState == self::EPP_VALIDATED)
		{
			$vars['loginname']  = SidnEppConfig::USERNAME;
			$vars['password']   = SidnEppConfig::PASSWORD;
			$xml = SidnTools::getXml('login.xml', $vars);
			if ($this->write($xml))
			{
				$response       = $this->parseXml($this->read());
				$responseXPath  = new DOMXPath($response);
				$code           = $responseXPath->query('/epp/response/result/@code');
				switch($code->item(0)->nodeValue)
				{
					case self::EPP_COMMAND_SUCCESSFUL:
						$this->connectionState = self::EPP_LOGGED_IN;
						return true;
						break;
					default:
						break;
				}
			}
			$this->doDisconnect();
			return false;
		}
		return false;

	}

	/**
	 * This will validate the EPP connection to make sure it's compatible with this client
	 *
	 * @return boolean
	 */
	private function validateConnection()
	{
		// don't validate if validation was already passed
		/*if ($this->connectionState > self::EPP_VALIDATED)
		{
			return true;
		}
		*/
		if ($this->connectionState == self::EPP_CONNECTED)
		{
			if ($this->doHello())
			{
				$domHello = $this->parseXml($this->read());
			}
		}
		else
		{
			$domHello = $this->parseXml($this->read());
		}

		if ($this->connectionState == self::EPP_HELLO_SENT || $this->connectionState == self::EPP_CONNECTED)
		{
			$this->connectionState = self::EPP_VALIDATING;
			if (is_object($domHello))
			{	
				$xPathHello = new DOMXPath($domHello);
				$svIdList    = $xPathHello->query('/epp/greeting/svID[1]');
				$versionList = $xPathHello->query('/epp/greeting/svcMenu[1]/version[1]');
				$buildList   = $xPathHello->query('/epp/greeting/svcMenu[1]/svcExtension[1]/extURI[1]');
				
				//Check if server is compatible
				if ($svIdList->item(0)->nodeValue != $this->eppServerName)
				{
					return false;
				}
				if ($versionList->item(0)->nodeValue != $this->eppServerVersion)
				{
					return false;
				}
				//if (substr($buildList->item(0)->nodeValue, -8) != $this->eppServerBuildVersion)
				if ($buildList->item(0)->nodeValue != $this->eppServerBuildVersion)
				{
					return false;
				}
				$this->connectionState = self::EPP_VALIDATED;
				return true;
			}
		}
		return false;
	}

	/**
	 * Test that all required classes are loaded
	 * @return array
	 */
	private function testClasses()
	{
		$requiredClasses = array();
		$requiredClasses['errorFound'] = 'false';

		// classes to test
		$requiredClasses['EppProtocol']					= 'false';
		$requiredClasses['SidnEppConfig']				= 'false';
		$requiredClasses['SidnContact']					= 'false';
		$requiredClasses['SidnContactInfo']				= 'false';
		$requiredClasses['SidnContactInfoResult']		= 'false';
		$requiredClasses['SidnContactPostalInfo']		= 'false';
		$requiredClasses['SidnCreateContactResult']		= 'false';
		$requiredClasses['SidnDomain']					= 'false';
		$requiredClasses['SidnDomainInfo']				= 'false';
		$requiredClasses['SidnDomainInfoResult']		= 'false';
		$requiredClasses['SidnEppException']			= 'false';
		$requiredClasses['SidnEppResult']				= 'false';
		$requiredClasses['SidnNameserverInfoResult']	= 'false';
		$requiredClasses['SidnNameserverInfo']			= 'false';
		$requiredClasses['SidnResponseMessage']			= 'false';
		$requiredClasses['SidnTools']					= 'false';

		$result = array();
		$result['errorFound'] = 'false';

		foreach ($requiredClasses as $requiredClass=>$requiredClassLoaded)
		{
			// skip errorFound entry
			if ($requiredClass == 'errorFound')
			{
				continue;
			}

			// test class existence
			if (class_exists($requiredClass))
			{
				$result[$requiredClass] = 'true';
			}
			else
			{
				$result[$requiredClass] = 'false';
				$result['errorFound'] = 'true';
			}
		}

		// success
		ksort($result);
		return $result;
	}

	/**
	 * Test the connection to the SIDN interface
	 * @return array
	 */
	private function testConnection()
	{
		$result = array();
		$connectionResult = $this->isAvailable();
		if ($connectionResult instanceof SidnEppResult && $connectionResult->getStatus() == SidnEppResult::SUCCESS)
		{
			$result['errorFound'] = 'false';
		}
		else
		{
			$result['errorFound'] = 'true';
		}

		return $result;
	}

	/**
	 * Test that  all required constants are filled in
	 */
	private function testConstants()
	{
		$result = array();
		$result['errorFound'] = false;

		$result['USERNAME']			= 'false';
		$result['PASSWORD']			= 'false';
		$result['RUN_PATH']			= 'false';
		$result['SERVER_ADDRESS']	= 'false';
		$result['SERVER_PORT']		= 'false';
		$result['XML_PATH']			= 'false';
		$result['TIMEOUT']			= 'false';

		foreach ($result as $key=>$value)
		{
			// skip errorFound entry
			if ($key == 'errorFound')
			{
				continue;
			}

			$constantValue = constant('SidnEppConfig' . '::' . $key );

			if ($constantValue && $constantValue != '')
			{
				$result[$key] = 'true';
			}
			else
			{
				$result['errorFound'] = 'true';
			}
		}

		// success
		ksort($result);
		return $result;
	}

	/**
	 * Display the diagnostic results from the functions:
	 *  $this->testClasses()
	 *  $this->testConstants()
	 *  $this->testConnection()
	 *
	 * @param array $classResult
	 * @param array $constantsResult
	 * @param array $connectionResult
	 * @return bool
	 */
	private function displayTests($classResult, $constantsResult, $connectionResult)
	{
		$header = '<html><body>';
		$tailer = '</body></html>';
		$title  = '<h1>php5-sidnepp client diagnostics</h1><br/>';
		$output = $header . $title;
		if ($classResult)
		{
			$output .= $this->displayClassTest($classResult);
		}
		else
		{
			$output .= '<red><h2>CLASSES NOT TESTED</h2></red><br/>';
		}

		if ($constantsResult)
		{
			$output .= $this->displayConstantsTest($constantsResult);
		}
		else
		{
			$output .= "<red><h2>CONSTANTS NOT TESTED</h2></red><br/>\n";
		}

		if ($connectionResult)
		{
			$output .= $this->displayConnectionTest($connectionResult);
		}
		else
		{
			$output .= "<red><h2>CONNECTION NOT TESTED</h2></red><br/>\n";
		}

		// finalize
		$output .= $tailer;

		// output
		echo $output;

		return true;
	}

	/**
	 * Return html code with the diagnostic results of the submitted results from $this->testClass()
	 * @param array $classResult
	 * @return string
	 */
	private function displayClassTest($classResult)
	{
		// build table for class test results
		$classRows = '';
		$classes = '<h2>Classes</h2>'
			. '<table>';

		foreach ($classResult as $requiredClassName=>$requiredClassLoaded)
		{
			if ($requiredClassName == 'errorFound')
			{
				continue;
			}

			if ($requiredClassLoaded == 'true')
			{
				$classLoad = '<font color="green">OK</font>';
			}
			else
			{
				$classLoad = '<font color="red">FAIL</font>';
			}

			$classRows .= '<tr><td>'.$requiredClassName . '</td><td>' . $classLoad . '</td></tr>';
		}

		return $classes . $classRows . '</table>';
	}

	/**
	 * Return html code with the diagnostic results of the submitted results from $this->testConstants()
	 * @param array $constantsResult
	 * @return string
	 */
	private function displayConstantsTest($constantsResult)
	{
		// build table for class test results
		$testRows = '';
		$constants = '<br/><h2>Constants</h2>'
			. '<table>';

		foreach ($constantsResult as $requiredName=>$testResult)
		{
			if ($requiredName == 'errorFound')
			{
				continue;
			}

			if ($testResult == 'true')
			{
				$testDisplay = '<font color="green">OK</font>';
			}
			else
			{
				$testDisplay = '<font color="red">FAIL</font>';
			}

			$testRows .= '<tr><td>'.$requiredName . '</td><td>' . $testDisplay . '</td></tr>';
		}

		return $constants . $testRows . '</table>';
	}

	/**
	 * Return html code with the diagnostic results of the submitted results from $this->testConnection()
	 * @param array $connectionResult
	 * @return string
	 */
	private function displayConnectionTest($connectionResult)
	{
		// build table for class test results
		$connection = '<br/><h2>Connection to SIDN interface</h2>'
			. '<table>';

		if ($connectionResult['errorFound'] == 'false')
		{
			$testDisplay = '<font color="green">OK</font>';
		}
		else
		{
			$testDisplay = $testDisplay = '<font color="red">FAIL</font>';
		}

		$testRow = '<tr><td>Connection</td><td>' . $testDisplay . '</td></tr>';

		return $connection . $testRow . '</table>';
	}
}

